library PipedreamSSNOTDAEdition

    globals
        private integer secret1 = 0
        private integer secret2 = 0
    endglobals

    public function SetSecret takes integer i, integer j returns nothing
        set secret1 = i
        set secret2 = j
    endfunction
    
    private struct BigNum_l
       integer leaf
       BigNum_l next
        debug static integer nalloc = 0
        
        static method create takes nothing returns BigNum_l
            local BigNum_l bl = BigNum_l.allocate()
            set bl.next = 0
            set bl.leaf = 0
            debug set BigNum_l.nalloc = BigNum_l.nalloc + 1
            return bl
        endmethod
        method onDestroy takes nothing returns nothing
            debug set BigNum_l.nalloc = BigNum_l.nalloc - 1
        endmethod
       
       //true:  want destroy
       method Clean takes nothing returns boolean
          if .next == 0 and .leaf == 0 then
             return true
          elseif .next != 0 and .next.Clean() then
             call .next.destroy()
             set .next = 0
             return .leaf == 0
          else
             return false
          endif
       endmethod
       
       method DivSmall takes integer base, integer denom returns integer
          local integer quotient
          local integer remainder = 0
          local integer num
          
          if .next != 0 then
             set remainder = .next.DivSmall(base,denom)
          endif
          
            set num = .leaf + remainder*base
          set quotient = num/denom
          set remainder = num - quotient*denom
          set .leaf = quotient
          return remainder
       endmethod
    endstruct

    private struct BigNumXX
        BigNum_l list
        integer base
        
        static method create takes integer base returns thistype
            local thistype b = thistype.allocate()
            set b.list = 0
            set b.base = base
            return b
        endmethod

       method onDestroy takes nothing returns nothing
          local BigNum_l cur = .list
          local BigNum_l next
          loop
             exitwhen cur == 0
             set next = cur.next
             call cur.destroy()
             set cur = next
          endloop
       endmethod
       
       method IsZero takes nothing returns boolean
          local BigNum_l cur = .list
          loop
             exitwhen cur == 0
             if cur.leaf != 0 then
                return false
             endif
             set cur = cur.next
          endloop
          return true
       endmethod
       
       method Dump takes nothing returns nothing
          local BigNum_l cur
          local string s = ""
          loop
             exitwhen cur == 0
             set s = I2S(cur.leaf)+" "+s
             set cur = cur.next
          endloop
          call BJDebugMsg(s)
       endmethod
       
       method Clean takes nothing returns nothing
          local BigNum_l cur = .list
          call cur.Clean()
       endmethod
       
       //fails if bignum is null
       //BASE() + carry must be less than MAXINT()
       method AddSmall takes integer carry returns nothing
          local BigNum_l next
          local BigNum_l cur = .list
          local integer sum
          
          if cur == 0 then
             set cur = BigNum_l.create()
             set .list = cur
          endif
          
          loop
             exitwhen carry == 0
             set sum = cur.leaf + carry
             set carry = sum / .base
             set sum = sum - carry*.base
             set cur.leaf = sum
             
             if cur.next == 0 then
                set cur.next = BigNum_l.create()
             endif
             set cur = cur.next
          endloop
       endmethod
       
       //x*BASE() must be less than MAXINT()
       method MulSmall takes integer x returns nothing
          local BigNum_l cur = .list
          local integer product
          local integer remainder
          local integer carry = 0
          loop
             exitwhen cur == 0 and carry == 0
             set product = x * cur.leaf + carry
             set carry = product/.base
             set remainder = product - carry*.base
             set cur.leaf = remainder
             if cur.next == 0 and carry != 0 then
                set cur.next = BigNum_l.create()
             endif
             set cur = cur.next
          endloop
       endmethod
       
       //Returns remainder
       method DivSmall takes integer denom returns integer
          return .list.DivSmall(.base,denom)
       endmethod
       
       method LastDigit takes nothing returns integer
          local BigNum_l cur = .list
          local BigNum_l next
          loop
             set next = cur.next
             exitwhen next == 0
             set cur = next
          endloop
          return cur.leaf
       endmethod
    endstruct
    
    //Colorize colors
    private constant function uppercolor takes nothing returns string
       return "|cff1e90ff"
    endfunction
    private constant function lowercolor takes nothing returns string
       return "|cff828250"
    endfunction
    private constant function numcolor takes nothing returns string
       return "|cffdaa520"
    endfunction

    private function player_charset takes nothing returns string
       return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    endfunction
    private function player_charsetlen takes nothing returns integer
       return StringLength(player_charset())
    endfunction

    private function charset takes nothing returns string
       return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    //   return "0123456789"
    endfunction
    private function charsetlen takes nothing returns integer
       return StringLength(charset())
    endfunction
    private function BASE takes nothing returns integer
       return charsetlen()
    endfunction

    private constant function HASHN takes nothing returns integer
       return 5000      //1./HASHN() is the probability of a random code being valid
    endfunction

    private constant function MAXINT takes nothing returns integer
       return 2147483647
    endfunction

    private function player_chartoi takes string c returns integer
       local integer i = 0
       local string cs = player_charset()
       local integer len = player_charsetlen()
       loop
          exitwhen i>=len or c == SubString(cs,i,i+1)
          set i = i + 1
       endloop
       return i
    endfunction

    private function chartoi takes string c returns integer
       local integer i = 0
       local string cs = charset()
       local integer len = charsetlen()
       loop
          exitwhen i>=len or c == SubString(cs,i,i+1)
          set i = i + 1
       endloop
       return i
    endfunction

    private function itochar takes integer i returns string
       return SubString(charset(),i,i+1)
    endfunction

    //You probably want to use a different char set for this
    //Also, use a hash that doesn't suck so much
    private function scommhash takes string s returns integer
       local integer array count
       local integer i = 0
       local integer len = StringLength(s)
       local integer x
        // return StringHash(s)
       set s = StringCase(s,true)
       loop
          exitwhen i >= len
          set x = player_chartoi(SubString(s,i,i+1))
          set count[x] = count[x] + 1
          set i = i + 1
       endloop
       set i = 0
       set len = player_charsetlen()
       set x = 0
       loop
          exitwhen i>= len
          set x = count[i]*count[i]*i+count[i]*x+x+173
    //      call BJDebugMsg(I2S(x)+" "+I2S(count[i]))
    //      call TriggerSleepAction(0.)
          set i = i + 1
       endloop
       if x < 0 then
          set x = -x
       endif
       return x
    endfunction

    private function modb takes integer x returns integer
       if x >= BASE() then
          return x - BASE()
       elseif x < 0 then
          return x + BASE()
       else
          return x
       endif
    endfunction

    
    //prefer algebraic approach because of real subtraction issues
    private function logXX takes real y, real base returns real
        local real x
        local real factor = 1.0
        local real logy = 0.0
        local real sign = 1.0
        if(y < 0.) then
            return 0.0
        endif
        if(y < 1.) then
            set y = 1.0/y
            set sign = -1.0
        endif
        //Chop out powers of the base
        loop
            exitwhen y < 1.0001    //decrease this ( bounded below by 1) to improve precision
            if(y > base) then
                set y = y / base
                set logy = logy + factor
            else
                set base = SquareRoot(base)     //If you use just one base a lot, precompute its squareroots
                set factor = factor / 2.
            endif
        endloop
        return sign*logy
    endfunction
    
	
    private function isupper takes string c returns boolean
       return c == StringCase(c,true)
    endfunction

    private function ischar takes string c returns boolean
       return S2I(c) == 0 and c!= "0" and c != "-"
    endfunction
	
    private function isnumber takes string c returns boolean
       return S2I(c) > 0 or c== "0"
    endfunction
	
    private function chartype takes string c returns integer
       if(ischar(c)) then
          if isupper(c) then
             return 0
          else
             return 1
          endif
       elseif isnumber(c) then
          return 2
       endif
	   return 3
    endfunction

  
	
    struct Savecode
       real digits      //logarithmic approximation
       BigNumXX bignum
        
       static method create takes nothing returns Savecode
          local Savecode sc = Savecode.allocate()
          set sc.digits = 0.
          set sc.bignum = BigNumXX.create(BASE())
          return sc
       endmethod
        
        method onDestroy takes nothing returns nothing
          call .bignum.destroy()
       endmethod

       method Encode takes integer val, integer max returns integer
          set .digits = .digits + logXX(max+1,BASE())
          call .bignum.MulSmall(max+1)
          call .bignum.AddSmall(val)
		  return val
       endmethod
       
       method Decode takes integer max returns integer
          return .bignum.DivSmall(max+1)
       endmethod
       
       method IsEmpty takes nothing returns boolean
          return .bignum.IsZero()
       endmethod
       
       method Length takes nothing returns real
          return .digits
       endmethod
       
       method Clean takes nothing returns nothing
          call .bignum.Clean()
       endmethod
        
           //These functions get too intimate with BigNum_l
       method Pad takes nothing returns nothing
          local BigNum_l cur = .bignum.list
          local BigNum_l prev
          local integer maxlen = R2I(1.0 + .Length())
          
          loop
             exitwhen cur == 0
             set prev = cur
             set cur = cur.next
             set maxlen = maxlen - 1
          endloop
          loop
             exitwhen maxlen <= 0
             set prev.next = BigNum_l.create()
             set prev = prev.next
             set maxlen = maxlen - 1
          endloop
       endmethod
       
       method ToString takes nothing returns string
          local BigNum_l cur = .bignum.list
          local string s = ""
          loop
             exitwhen cur == 0
             set s = itochar(cur.leaf) + s
             set cur = cur.next
          endloop
          return s
       endmethod
       
       method FromString takes string s returns nothing
          local integer i = StringLength(s)-1
          local BigNum_l cur = BigNum_l.create()
          set .bignum.list = cur
          loop
             set cur.leaf = chartoi(SubString(s,i,i+1))      
             exitwhen i <= 0
             set cur.next = BigNum_l.create()
             set cur = cur.next
             set i = i - 1
          endloop
       endmethod
       
       method Hash takes nothing returns integer
          local integer hash = 0
          local integer x
          local BigNum_l cur = .bignum.list
          loop
             exitwhen cur == 0
             set x = cur.leaf
             set hash = ModuloInteger(hash+37*hash/(x+1) + 357*x/(1+hash - (hash/BASE())*BASE()) + secret2,HASHN())
             set cur = cur.next
          endloop
          return hash
       endmethod

       //this is not cryptographic which is fine for this application
       //sign = 1 is forward
       //sign = -1 is backward
       method Obfuscate takes integer key, integer sign returns nothing
          local integer seed = GetRandomInt(0,MAXINT())
          local integer advance
          local integer x
          local BigNum_l cur = .bignum.list
       
       
          if sign == -1 then
             call SetRandomSeed(.bignum.LastDigit())
             set cur.leaf = modb(cur.leaf + sign*GetRandomInt(0,BASE()-1))
             set x = cur.leaf
          endif
          
          call SetRandomSeed(key)
          loop
             exitwhen cur == 0
             
             if sign == -1 then
                set advance = cur.leaf
             endif
             set cur.leaf = modb(cur.leaf + sign*GetRandomInt(0,BASE()-1))
             if sign == 1 then
                set advance = cur.leaf
             endif
             set advance = advance + GetRandomInt(0,BASE()-1)
             call SetRandomSeed(advance)
             
             set x = cur.leaf
             set cur = cur.next
          endloop
          
          if sign == 1 then
             call SetRandomSeed(x)
             set .bignum.list.leaf = modb(.bignum.list.leaf + sign*GetRandomInt(0,BASE()-1))
          endif
          
          call SetRandomSeed(seed)
       endmethod
       
       method Dump takes nothing returns nothing
          local BigNum_l cur = .bignum.list
          local string s = ""
          set s = "max: "+R2S(.digits)
          
          loop
             exitwhen cur == 0
             set s = I2S(cur.leaf)+" "+s
             set cur = cur.next
          endloop
          call BJDebugMsg(s)
       endmethod
       
       method Save takes string playerName, integer loadtype returns string
          local integer key = scommhash(playerName)+loadtype*secret1
          local string s
          local integer hash
          call .Clean()
          set hash = .Hash()
          call .Encode(hash,HASHN())
          call .Clean()
          
          /////////////////////// Save code information.  Comment out next two lines in implementation
          //call BJDebugMsg("Expected length: " +I2S(R2I(1.0+.Length())))
          //call BJDebugMsg("Room left in last char: "+R2S(1.-ModuloReal((.Length()),1)))
          ///////////////////////
          
          call .Pad()
          call .Obfuscate(key,1)
          return .ToString()
       endmethod

	   
	   static method  colorize takes string s returns string
		   local string out = ""
		   local integer i = 0
		   local integer len = StringLength(s)
		   local integer ctype
		   local string c
		   loop
			  exitwhen i >= len
			  set c = SubString(s,i,i+1)
			  set ctype = chartype(c)
			  if ctype == 0 then
				 set out = out + uppercolor()+c+"|r"
			  elseif ctype == 1 then
				 set out = out + lowercolor()+c+"|r"
			  elseif ctype == 2 then
				 set out = out + numcolor()+c+"|r"
			  else
		  		 set out = out + c
			  endif
			  set i = i + 1
		   endloop
		   return out
		endmethod
	
       method Load takes string playerName, string s, integer loadtype returns boolean
          local integer ikey = scommhash(playerName)+loadtype*secret1
          local integer inputhash
          
          call .FromString(s)
          call .Obfuscate(ikey,-1)
          set inputhash = .Decode(HASHN())
          
          call .Clean()
          
          return inputhash == .Hash()
       endmethod
    endstruct

    
    
    
    
   

endlibrary